﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using LitJson;

[AddComponentMenu("PUEX/Affector/Rotation/Orbit")]
public class PUEXOrbitAffector : PUEXAffector {

	public enum OrbitChainMode {
		OCM_Add,
		OCM_Relat,
		OCM_Abs,
	}

	#region - Editable property
	public OrbitChainMode chainMode;
	public PUEXDynamicAttribute offsetAmount;
	public PUEXDynamicAttribute rotationAmount;
	public Vector3 rotationAxis;
	public string rotationAxisParamName = "";
	public bool useEmitterTime;
	public bool alignDirection;
	public bool tangentDirection;
	public bool inWorldSpace;
	public bool applyOwnerScale;
	#endregion

	#region - Property
	protected Vector3 _rotationAxis;
	#endregion
	
	#region - Lifecycle
	public PUEXOrbitAffector ()
	{
		chainMode = OrbitChainMode.OCM_Add;
		offsetAmount = PUEXDynamicAttribute.GenerateCurved(0f, 1f);
		rotationAmount = PUEXDynamicAttribute.GenerateCurved(0f, 1f);
		rotationAxis = Vector3.up;
		useEmitterTime = false;
		alignDirection = true;
		tangentDirection = true;
		inWorldSpace = true;
		applyOwnerScale = true;
	}
	#endregion
	
	#region - Update
	public override void preUpdate (float dt)
	{
		base.preUpdate (dt);

		_rotationAxis = rotationAxis;
		if (inWorldSpace) {
			if (ownerEmitter.keepLocal) {
				Matrix4x4 invMat = ownerEmitter.ownerParticleSystem.systemToWorldTransform.inverse;
				_rotationAxis = invMat.MultiplyVector(rotationAxis); 
			}
		} else {
			if (!ownerEmitter.keepLocal) {
				_rotationAxis = ownerEmitter.ownerParticleSystem.systemToWorldTransform.MultiplyVector(rotationAxis);
			}
		}
	}

	public override void update (float dt, PUEXParticle particle)
	{
		// time
		float t = particle.timeFraction;
		if (useEmitterTime)
			t = inTrigger() ? triggerTimeFraction : ownerEmitter.timeFraction;

		// offset
		Vector3 originalPosition = particle.originalPosition;
		float o = 1f;
		if (applyOwnerScale) {
			o += offsetAmount.getValue (t) * ownerEmitter.ownerParticleSystem.systemToWorldScale;
		} else {
			o += offsetAmount.getValue (t);
		}
		if (o != 1f) {
			Vector3 cp = PUEXUtils.closestPointOnVector(Vector3.zero, _rotationAxis, originalPosition);
			originalPosition = cp + (originalPosition - cp) * o;
		}

		// rotation
		Quaternion r = Quaternion.AngleAxis (360f * rotationAmount.getValue(t), _rotationAxis);
		Matrix4x4 mat = Matrix4x4.TRS (Vector3.zero, r, Vector3.one);

		Vector3 newPosition = mat.MultiplyPoint(originalPosition);

		// position
		switch (chainMode) {
		case OrbitChainMode.OCM_Add: {
			newPosition = particle.position + (newPosition - particle.lastPosition) * dt;
			break;
		}
		case OrbitChainMode.OCM_Relat: {
			newPosition = particle.position + (newPosition - particle.originalPosition) * dt;
			break;
		}
		case OrbitChainMode.OCM_Abs: {
			// nothing to do
			break;
		}
		}

		// direction
		if (alignDirection) {
			if (tangentDirection) {
				particle.direction = (newPosition - particle.position).normalized;
			} else {
				Vector3 cp = PUEXUtils.closestPointOnVector(Vector3.zero, _rotationAxis, newPosition);
				particle.direction = (newPosition - cp).normalized;
			}
		}
		
		particle.position = newPosition;
	}
	#endregion

	#region - Param property
	public override void updateParams (PUEXParticleSystem system)
	{
		offsetAmount.parameterize (system);
		rotationAmount.parameterize (system);
		Vector3 value3;
		if (system.getParamVec3 (rotationAxisParamName, out value3)) {
			rotationAxis = value3;
		}
	}
	#endregion

	#region - Serialization
	public override JsonData save (JsonData emitterData, JsonData systemData)
	{
		JsonData ret = base.save (emitterData, systemData);

		if (chainMode == OrbitChainMode.OCM_Add) {
			ret ["chain_mode"] = "add";
		} else if (chainMode == OrbitChainMode.OCM_Relat) {
			ret ["chain_mode"] = "relat";
		} else if (chainMode == OrbitChainMode.OCM_Abs) {
			ret ["chain_mode"] = "abs";
		}
		ret ["offset_amount"] = PUEXUtils.serializeDynamicAttribute (offsetAmount);
		ret ["rotation_amount"] = PUEXUtils.serializeDynamicAttribute (rotationAmount);
		ret ["rotation_axis"] = PUEXUtils.serializeVector3 (rotationAxis);
		ret ["rotation_param_name"] = rotationAxisParamName;
		ret ["use_emitter_time"] = useEmitterTime;
		ret ["align_direction"] = alignDirection;
		ret ["tangent_direction"] = tangentDirection;
		ret ["in_world_space"] = inWorldSpace;
		ret ["apply_owner_scale"] = applyOwnerScale;
		
		return ret;
	}
	#endregion

}
